﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information
// 
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 

using System;
using System.Collections;
using System.Diagnostics;

namespace log4net.Util
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="source"></param>
    /// <param name="e"></param>
    public delegate void LogReceivedEventHandler(object source, LogReceivedEventArgs e);

    /// <summary>Outputs log statements from within the log4net assembly.</summary>
    /// <remarks>
    /// <para>
    /// Log4net components cannot make log4net logging calls. However, it is
    /// sometimes useful for the user to learn about what log4net is
    /// doing.
    /// </para>
    /// <para>
    /// All log4net internal debug calls go to the standard output stream
    /// whereas internal error messages are sent to the standard error output 
    /// stream.
    /// </para>
    /// </remarks>
    /// <author>Nicko Cadell</author>
    /// <author>Gert Driesen</author>
    public sealed class LogLog
    {
        /// <summary>The event raised when an internal message has been received.</summary>
        public static event LogReceivedEventHandler LogReceived;

        private readonly Type source;
        private readonly DateTime timeStampUtc;
        private readonly string prefix;
        private readonly string message;
        private readonly Exception exception;

        /// <summary>The Type that generated the internal message.</summary>
        public Type Source
        {
            get { return this.source; }
        }

        /// <summary>The DateTime stamp of when the internal message was received.</summary>
        public DateTime TimeStamp
        {
            get { return this.timeStampUtc.ToLocalTime(); }
        }

        /// <summary>The UTC DateTime stamp of when the internal message was received.</summary>
        public DateTime TimeStampUtc
        {
            get { return this.timeStampUtc; }
        }

        /// <summary>A string indicating the severity of the internal message.</summary>
        /// <remarks>
        /// "log4net: ", 
        /// "log4net:ERROR ", 
        /// "log4net:WARN "
        /// </remarks>
        public string Prefix
        {
            get { return this.prefix; }
        }

        /// <summary>The internal log message.</summary>
        public string Message
        {
            get { return this.message; }
        }

        /// <summary>The Exception related to the message.</summary>
        /// <remarks>
        /// Optional. Will be null if no Exception was passed.
        /// </remarks>
        public Exception Exception
        {
            get { return this.exception; }
        }

        /// <summary>
        /// Formats Prefix, Source, and Message in the same format as the value
        /// sent to Console.Out and Trace.Write.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.Prefix + this.Source.Name + ": " + this.Message;
        }

        /// <summary>Initializes a new instance of the <see cref="LogLog" /> class. </summary>
        /// <param name="source"></param>
        /// <param name="prefix"></param>
        /// <param name="message"></param>
        /// <param name="exception"></param>
        public LogLog(Type source, string prefix, string message, Exception exception)
        {
            this.timeStampUtc = DateTime.UtcNow;
            
            this.source = source;
            this.prefix = prefix;
            this.message = message;
            this.exception = exception;
        }

        /// <summary>
        /// Static constructor that initializes logging by reading 
        /// settings from the application configuration file.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The <c>log4net.Internal.Debug</c> application setting
        /// controls internal debugging. This setting should be set
        /// to <c>true</c> to enable debugging.
        /// </para>
        /// <para>
        /// The <c>log4net.Internal.Quiet</c> application setting
        /// suppresses all internal logging including error messages. 
        /// This setting should be set to <c>true</c> to enable message
        /// suppression.
        /// </para>
        /// </remarks>
        static LogLog()
        {
#if !NETCF
            try
            {
                InternalDebugging = OptionConverter.ToBoolean(SystemInfo.GetAppSetting("log4net.Internal.Debug"), false);
                QuietMode = OptionConverter.ToBoolean(SystemInfo.GetAppSetting("log4net.Internal.Quiet"), false);
                EmitInternalMessages = OptionConverter.ToBoolean(SystemInfo.GetAppSetting("log4net.Internal.Emit"), true);
            }
            catch(Exception ex)
            {
                // If an exception is thrown here then it looks like the config file does not
                // parse correctly.
                //
                // We will leave debug OFF and print an Error message
                Error(typeof(LogLog), "Exception while reading ConfigurationSettings. Check your .config file is well formed XML.", ex);
            }
#endif
        }

        /// <summary>
        /// Gets or sets a value indicating whether log4net internal logging
        /// is enabled or disabled.
        /// </summary>
        /// <value>
        /// <c>true</c> if log4net internal logging is enabled, otherwise 
        /// <c>false</c>.
        /// </value>
        /// <remarks>
        /// <para>
        /// When set to <c>true</c>, internal debug level logging will be 
        /// displayed.
        /// </para>
        /// <para>
        /// This value can be set by setting the application setting 
        /// <c>log4net.Internal.Debug</c> in the application configuration
        /// file.
        /// </para>
        /// <para>
        /// The default value is <c>false</c>, i.e. debugging is
        /// disabled.
        /// </para>
        /// </remarks>
        /// <example>
        /// <para>
        /// The following example enables internal debugging using the 
        /// application configuration file :
        /// </para>
        /// <code lang="XML" escaped="true">
        /// <configuration>
        ///         <appSettings>
        ///             <add key="log4net.Internal.Debug" value="true" />
        ///         </appSettings>
        /// </configuration>
        /// </code>
        /// </example>
        public static bool InternalDebugging
        {
            get { return s_debugEnabled; }
            set { s_debugEnabled = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether log4net should generate no output
        /// from internal logging, not even for errors. 
        /// </summary>
        /// <value>
        /// <c>true</c> if log4net should generate no output at all from internal 
        /// logging, otherwise <c>false</c>.
        /// </value>
        /// <remarks>
        /// <para>
        /// When set to <c>true</c> will cause internal logging at all levels to be 
        /// suppressed. This means that no warning or error reports will be logged. 
        /// This option overrides the <see cref="InternalDebugging"/> setting and 
        /// disables all debug also.
        /// </para>
        /// <para>This value can be set by setting the application setting
        /// <c>log4net.Internal.Quiet</c> in the application configuration file.
        /// </para>
        /// <para>
        /// The default value is <c>false</c>, i.e. internal logging is not
        /// disabled.
        /// </para>
        /// </remarks>
        /// <example>
        /// The following example disables internal logging using the 
        /// application configuration file :
        /// <code lang="XML" escaped="true">
        /// <configuration>
        ///         <appSettings>
        ///             <add key="log4net.Internal.Quiet" value="true" />
        ///         </appSettings>
        /// </configuration>
        /// </code>
        /// </example>
        public static bool QuietMode
        {
            get { return s_quietMode; }
            set { s_quietMode = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public static bool EmitInternalMessages
        {
            get { return s_emitInternalMessages; }
            set { s_emitInternalMessages = value; }
        }

        /// <summary>Raises the LogReceived event when an internal messages is received.</summary>
        /// <param name="source"></param>
        /// <param name="prefix"></param>
        /// <param name="message"></param>
        /// <param name="exception"></param>
        public static void OnLogReceived(Type source, string prefix, string message, Exception exception)
        {
            if (LogReceived != null)
            {
                LogReceived(null, new LogReceivedEventArgs(new LogLog(source, prefix, message, exception)));
            }
        }

        /// <summary>Test if LogLog.Debug is enabled for output.</summary>
        /// <value>
        /// <c>true</c> if Debug is enabled
        /// </value>
        /// <remarks>
        /// <para>
        /// Test if LogLog.Debug is enabled for output.
        /// </para>
        /// </remarks>
        public static bool IsDebugEnabled
        {
            get { return s_debugEnabled && !s_quietMode; }
        }

        /// <summary>
        /// Writes log4net internal debug messages to the 
        /// standard output stream.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="message">The message to log.</param>
        /// <remarks>
        /// <para>
        ///     All internal debug messages are prepended with 
        ///     the string "log4net: ".
        /// </para>
        /// </remarks>
        public static void Debug(Type source, string message) 
        {
            if (IsDebugEnabled) 
            {
                if (EmitInternalMessages)
                {
                    EmitOutLine(PREFIX + message);
                }

                OnLogReceived(source, PREFIX, message, null);
            }
        }

        /// <summary>
        /// Writes log4net internal debug messages to the 
        /// standard output stream.
        /// </summary>
        /// <param name="source">The Type that generated this message.</param>
        /// <param name="message">The message to log.</param>
        /// <param name="exception">An exception to log.</param>
        /// <remarks>
        /// <para>
        ///     All internal debug messages are prepended with 
        ///     the string "log4net: ".
        /// </para>
        /// </remarks>
        public static void Debug(Type source, string message, Exception exception) 
        {
            if (IsDebugEnabled) 
            {
                if (EmitInternalMessages)
                {
                    EmitOutLine(PREFIX + message);
                    if (exception != null)
                    {
                        EmitOutLine(exception.ToString());
                    }
                }

                OnLogReceived(source, PREFIX, message, exception);
            }
        }
  
        /// <summary>Test if LogLog.Warn is enabled for output.</summary>
        /// <value>
        /// <c>true</c> if Warn is enabled
        /// </value>
        /// <remarks>
        /// <para>
        /// Test if LogLog.Warn is enabled for output.
        /// </para>
        /// </remarks>
        public static bool IsWarnEnabled
        {
            get { return !s_quietMode; }
        }

        /// <summary>
        /// Writes log4net internal warning messages to the 
        /// standard error stream.
        /// </summary>
        /// <param name="source">The Type that generated this message.</param>
        /// <param name="message">The message to log.</param>
        /// <remarks>
        /// <para>
        ///     All internal warning messages are prepended with 
        ///     the string "log4net:WARN ".
        /// </para>
        /// </remarks>
        public static void Warn(Type source, string message) 
        {
            if (IsWarnEnabled)
            {
                if (EmitInternalMessages)
                {
                    EmitErrorLine(WARN_PREFIX + message);
                }

                OnLogReceived(source, WARN_PREFIX, message, null);
            }
        }  

        /// <summary>
        /// Writes log4net internal warning messages to the 
        /// standard error stream.
        /// </summary>
        /// <param name="source">The Type that generated this message.</param>
        /// <param name="message">The message to log.</param>
        /// <param name="exception">An exception to log.</param>
        /// <remarks>
        /// <para>
        ///     All internal warning messages are prepended with 
        ///     the string "log4net:WARN ".
        /// </para>
        /// </remarks>
        public static void Warn(Type source, string message, Exception exception) 
        {
            if (IsWarnEnabled)
            {
                if (EmitInternalMessages)
                {
                    EmitErrorLine(WARN_PREFIX + message);
                    if (exception != null)
                    {
                        EmitErrorLine(exception.ToString());
                    }
                }

                OnLogReceived(source, WARN_PREFIX, message, exception);
            }
        } 

        /// <summary>Test if LogLog.Error is enabled for output.</summary>
        /// <value>
        /// <c>true</c> if Error is enabled
        /// </value>
        /// <remarks>
        /// <para>
        /// Test if LogLog.Error is enabled for output.
        /// </para>
        /// </remarks>
        public static bool IsErrorEnabled
        {
            get { return !s_quietMode; }
        }

        /// <summary>
        /// Writes log4net internal error messages to the 
        /// standard error stream.
        /// </summary>
        /// <param name="source">The Type that generated this message.</param>
        /// <param name="message">The message to log.</param>
        /// <remarks>
        /// <para>
        ///     All internal error messages are prepended with 
        ///     the string "log4net:ERROR ".
        /// </para>
        /// </remarks>
        public static void Error(Type source, string message) 
        {
            if (IsErrorEnabled)
            {
                if (EmitInternalMessages)
                {
                    EmitErrorLine(ERR_PREFIX + message);
                }

                OnLogReceived(source, ERR_PREFIX, message, null);
            }
        }  

        /// <summary>
        /// Writes log4net internal error messages to the 
        /// standard error stream.
        /// </summary>
        /// <param name="source">The Type that generated this message.</param>
        /// <param name="message">The message to log.</param>
        /// <param name="exception">An exception to log.</param>
        /// <remarks>
        /// <para>
        ///     All internal debug messages are prepended with 
        ///     the string "log4net:ERROR ".
        /// </para>
        /// </remarks>
        public static void Error(Type source, string message, Exception exception) 
        {
            if (IsErrorEnabled)
            {
                if (EmitInternalMessages)
                {
                    EmitErrorLine(ERR_PREFIX + message);
                    if (exception != null)
                    {
                        EmitErrorLine(exception.ToString());
                    }
                }

                OnLogReceived(source, ERR_PREFIX, message, exception);
            }
        }

        /// <summary>Writes output to the standard output stream.  </summary>
        /// <param name="message">The message to log.</param>
        /// <remarks>
        /// <para>
        /// Writes to both Console.Out and System.Diagnostics.Trace.
        /// Note that the System.Diagnostics.Trace is not supported
        /// on the Compact Framework.
        /// </para>
        /// <para>
        /// If the AppDomain is not configured with a config file then
        /// the call to System.Diagnostics.Trace may fail. This is only
        /// an issue if you are programmatically creating your own AppDomains.
        /// </para>
        /// </remarks>
        private static void EmitOutLine(string message)
        {
            try
            {
#if NETCF
                Console.WriteLine(message);
                //System.Diagnostics.Debug.WriteLine(message);
#else
                Console.Out.WriteLine(message);
                Trace.WriteLine(message);
#endif
            }
            catch
            {
                // Ignore exception, what else can we do? Not really a good idea to propagate back to the caller
            }
        }

        /// <summary>Writes output to the standard error stream.  </summary>
        /// <param name="message">The message to log.</param>
        /// <remarks>
        /// <para>
        /// Writes to both Console.Error and System.Diagnostics.Trace.
        /// Note that the System.Diagnostics.Trace is not supported
        /// on the Compact Framework.
        /// </para>
        /// <para>
        /// If the AppDomain is not configured with a config file then
        /// the call to System.Diagnostics.Trace may fail. This is only
        /// an issue if you are programmatically creating your own AppDomains.
        /// </para>
        /// </remarks>
        private static void EmitErrorLine(string message)
        {
            try
            {
#if NETCF
                Console.WriteLine(message);
                //System.Diagnostics.Debug.WriteLine(message);
#else
                Console.Error.WriteLine(message);
                Trace.WriteLine(message);
#endif
            }
            catch
            {
                // Ignore exception, what else can we do? Not really a good idea to propagate back to the caller
            }
        }

        /// <summary> Default debug level</summary>
        private static bool s_debugEnabled = false;

        /// <summary>In quietMode not even errors generate any output.</summary>
        private static bool s_quietMode = false;

        private static bool s_emitInternalMessages = true;

        private const string PREFIX			= "log4net: ";
        private const string ERR_PREFIX		= "log4net:ERROR ";
        private const string WARN_PREFIX	= "log4net:WARN ";

        /// <summary>
        /// Subscribes to the LogLog.LogReceived event and stores messages
        /// to the supplied IList instance.
        /// </summary>
        public class LogReceivedAdapter : IDisposable
        {
            private readonly IList items;
            private readonly LogReceivedEventHandler handler;

            /// <summary>
            /// 
            /// </summary>
            /// <param name="items"></param>
            public LogReceivedAdapter(IList items)
            {
                this.items = items;

                this.handler = new LogReceivedEventHandler(this.LogLog_LogReceived);

                LogReceived += this.handler;
            }

            void LogLog_LogReceived(object source, LogReceivedEventArgs e)
            {
                this.items.Add(e.LogLog);
            }

            /// <summary>
            /// 
            /// </summary>
            public IList Items
            {
                get { return this.items; }
            }

            /// <summary>
            /// 
            /// </summary>
            public void Dispose()
            {
                LogReceived -= this.handler;
            }
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public class LogReceivedEventArgs : EventArgs
    {
        private readonly LogLog loglog;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="loglog"></param>
        public LogReceivedEventArgs(LogLog loglog)
        {
            this.loglog = loglog;
        }

        /// <summary>
        /// 
        /// </summary>
        public LogLog LogLog
        {
            get { return this.loglog; }
        }
    }
}
